﻿using BusinessObjects;
using DataAccess.EntityModels;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Diagnostics;
using System.Data.Objects;
using System.Data.Entity.Infrastructure;

namespace SampleSecuirtyApplication
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var db = new DatabaseContext())
            {
                var loginPermission = AddPermission(db, "User_Login");
                var createOthersUserPermission = AddPermission(db, "User_Create_Others");
                var deleteSelfUserPermission = AddPermission(db, "User_Delete_Self");
                var deleteOthersUserPermission = AddPermission(db, "User_Delete_Others");
                var updateSelfUserPermission = AddPermission(db, "User_Update_Self");
                var updateOthersUserPermission = AddPermission(db, "User_Update_Others");

                var adminRole = AddRole(db, "Administrator");
                AddPermissionToRole(db, createOthersUserPermission, adminRole);
                AddPermissionToRole(db, deleteOthersUserPermission, adminRole);
                AddPermissionToRole(db, updateOthersUserPermission, adminRole);

                var userRole = AddRole(db, "User");
                AddPermissionToRole(db, loginPermission, userRole);
                AddPermissionToRole(db, updateSelfUserPermission, userRole);

                var nonAdminUserRole = AddRole(db, "Non Admin User");
                AddPermissionToRole(db, deleteSelfUserPermission, nonAdminUserRole);

                var adminUser = AddUser(db, "Paul");
                AddUserToRole(db, userRole, adminUser);
                AddUserToRole(db, adminRole, adminUser);

                var nonAdminUser = AddUser(db, "John");
                AddUserToRole(db, userRole, nonAdminUser);
                AddUserToRole(db, nonAdminUserRole, nonAdminUser);

                var isAdminUserAdmin = IsUserInRole(db, adminRole, adminUser);
                var isAdminUserUser = IsUserInRole(db, userRole, adminUser);
                var isAdminUserNonAdmin = IsUserInRole(db, nonAdminUserRole, adminUser);
                var isNonAdminUserAdmin = IsUserInRole(db, adminRole, nonAdminUser);
                var isNonAdminUserUser = IsUserInRole(db, userRole, nonAdminUser);
                var isNonAdminUserNonAdminUser = IsUserInRole(db, nonAdminUserRole, nonAdminUser);

                var canPaulCreateOtherUsers = IsUserGrantedPermission(db, createOthersUserPermission, adminUser);

                var sw = new Stopwatch();
                sw.Start();
                var canJohnCreateOtherUsers = IsUserGrantedPermission(db, createOthersUserPermission, nonAdminUser);
                sw.Stop();
                Console.WriteLine(sw.Elapsed.Milliseconds);

                Console.WriteLine("Database created.");
                Console.ReadLine();
            }
        }

        private static bool IsUserGrantedPermission(DatabaseContext db, Permission permission, User user)
        {
            var userRoles = db.Roles.Where(r => r.RolesUsers.Any(ru => ru.UserId == user.Id));
            var userPerms = db.Permissions.Where(p => p.RolesPermissions.Any(rp => userRoles.Any(ur => ur.Id == rp.RoleId)));
            var sql = userPerms.ToString();
            Console.WriteLine(userPerms.ToString());
            return userPerms.Any(up => up.Id == permission.Id);
        }

        [Obsolete("Check if a user has been granted permission instead.")]
        private static bool IsUserInRole(DatabaseContext db, Role role, User user)
        {
            var selectedUser = db.Users.Include("RolesUsers").Single(u => u.Id == user.Id);
            return selectedUser.RolesUsers.Any(ru => ru.User == user && ru.Role == role);
        }

        private static void AddPermissionToRole(DatabaseContext db, Permission permission, Role role)
        {
            var givenPermission = db.Permissions.Include("RolesPermissions").Single(p => p.Id == permission.Id);

            var rolePermission = givenPermission.RolesPermissions.SingleOrDefault(rp => rp.PermissionId == permission.Id && rp.RoleId == role.Id);

            if (rolePermission == null)
            {
                rolePermission = new RolePermission();
                rolePermission.Permission = permission;
                rolePermission.Role = role;
                permission.RolesPermissions.Add(rolePermission);
                db.SaveChanges();
            }
        }

        private static void AddUserToRole(DatabaseContext db, Role role, User user)
        {
            var giventRole = db.Roles.Include("RolesUsers").Single(r => r.Id == role.Id);

            var roleUser = giventRole.RolesUsers.SingleOrDefault(ru => ru.RoleId == role.Id && ru.UserId == user.Id);

            if (roleUser == null)
            {
                roleUser = new RoleUser();
                roleUser.Role = role;
                roleUser.User = user;
                role.RolesUsers.Add(roleUser);
                db.SaveChanges();
            }
        }

        private static User AddUser(DatabaseContext db, string Name)
        {
            var existing = db.Users.FirstOrDefault(u => u.Name == Name);
            if (existing != null)
                return existing;

            var user = new User();
            user.Name = Name;
            db.Users.Add(user);
            db.SaveChanges();
            return user;
        }

        private static Permission AddPermission(DatabaseContext db, string Name)
        {
            var existing = db.Permissions.FirstOrDefault(p => p.Name == Name);
            if (existing != null)
                return existing;
            
            var permission = new Permission();
            permission.Name = Name;
            db.Permissions.Add(permission);
            db.SaveChanges();
            return permission;
        }

        private static Role AddRole(DatabaseContext db, string Name)
        {
            var existing = db.Roles.FirstOrDefault(r => r.Name == Name);
            if (existing != null)
                return existing;
                        
            var newRole = new Role();
            newRole.Name = Name;
            db.Roles.Add(newRole);
            db.SaveChanges();
            return newRole;
        }
    }
}
